package com.dxy.lc.utils;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.dxy.lc.auth.DefaultLoginUser;
import com.dxy.lc.auth.LoginUser;
import com.dxy.lc.auth.UserContextHolder;
import com.dxy.lc.base.AppConstant;
import com.dxy.lc.base.StatusMessageEnum;
import com.dxy.lc.base.UnifiedException;
import com.dxy.lc.enums.UserStatus;
import com.dxy.lc.manager.UserCacheManager;
import com.dxy.lc.mapper.UserMapper;
import com.dxy.lc.model.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
public class LoginUtil {

    public static String author;// 作者
    public static String module;// 模块
    public static String site;// 站点
    public static String issuer;// 签发人
    public static String secret;// 密钥
    public static long expire;// 失效时间，单位分钟
    private static UserMapper userMapper; // user表操作mapper

    @Value("${jwt.conf.author:dxy}")
    public void setAuthor(String author) {
        LoginUtil.author = author;
    }

    @Value("${jwt.conf.module:hms}")
    public void setModule(String module) {
        LoginUtil.module = module;
    }

    @Value("${jwt.conf.site:dxy.com}")
    public void setSite(String site) {
        LoginUtil.site = site;
    }

    @Value("${jwt.conf.issuer:dxy}")
    public void setIssuer(String issuer) {
        LoginUtil.issuer = issuer;
    }

    @Value("${jwt.conf.secret:fjioahgajgi}")
    public void setSecret(String secret) {
        LoginUtil.secret = secret;
    }

    @Value("${jwt.conf.expire:120}")
    public void setExpire(int expire) {
        LoginUtil.expire = expire;
    }

    @Autowired
    public void setUserMapper(UserMapper userMapper){
        LoginUtil.userMapper = userMapper;
    }

    private static final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;//签名算法

    private static SecretKey generateKey() {
        byte[] encodeKey = Base64.decodeBase64(Base64.encodeBase64(secret.getBytes()));
        return new SecretKeySpec(encodeKey, 0, encodeKey.length, "AES");// 获取加密key
    }

    /**
     * 缓存token
     */
    private static final Map<String, String> TOKEN_CACHE_MAP = new ConcurrentHashMap<>();

    /**
     * 设置token缓存
     */
    public static void setTokenCache(String userId, String token){
        Assert.notBlank(userId);
        Assert.notBlank(token);
        userMapper.updateById(new User()
                .setId(Integer.parseInt(userId))
                .setLastLoginTime(LocalDateTime.now())
                .setLastLoginToken(token)
                .setStatus(Integer.parseInt(UserStatus.ONLINE.toCode())));
        TOKEN_CACHE_MAP.put(userId, token);
    }

    /**
     * 获取缓存的token
     */
    public synchronized static String getCachedToken(String userId){
        String token = TOKEN_CACHE_MAP.get(userId);
        if (token != null){
            return token;
        }
        User user = userMapper.selectById(Integer.parseInt(userId));
        if (user == null){
            return null;
        }
        token = user.getLastLoginToken();
        if (token != null){
            TOKEN_CACHE_MAP.put(userId, token);
        }
        return token;
    }

    /**
     * 移除缓存的token
     */
    public static void delCachedToken(String userId) {
        // 这样写不能设置null值
        // userMapper.updateById(new User()
        //         .setId(Integer.parseInt(userId))
        //         .setLastLoginToken(null)
        //         .setStatus(Integer.parseInt(UserStatus.OFFLINE.toCode())));
        LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<User>()
                .set(User::getLastLoginToken, null)
                .set(User::getStatus, Integer.parseInt(UserStatus.OFFLINE.toCode()))
                .eq(User::getId, Integer.parseInt(userId));
        userMapper.update(null, wrapper);
        TOKEN_CACHE_MAP.remove(userId);
    }


    /**
     * 创建token
     * @param userId
     * @return
     */
    public static String createToken(String userId) {
        Date nowDate = new Date();
        Date expireDate = new Date(nowDate.getTime() + expire * 60 * 1000);
        Map<String, Object> header = new HashMap<>();
        header.put("typ", "JWT");
        header.put("alg", "HS256");
        Map<String, Object> claims = new HashMap<>();
        claims.put("module", module);
        claims.put("author", author);
        JwtBuilder jwtBuilder = Jwts.builder()
                .setHeader(header)//保存头信息
                .setClaims(claims)//保存附加数据
                .setId(UUID.randomUUID().toString().replace("-", ""))
                .setIssuedAt(nowDate)//签发时间
                .setIssuer(issuer)//设置签发者
                .setSubject(userId)//此处可以设置要传递的数据userid
                .signWith(signatureAlgorithm, generateKey())//设置签名算法
                .setExpiration(expireDate);//配置失效时间
        return jwtBuilder.compact();
    }

    /**
     * 解析token
     * @param token
     * @return
     */
    public static Jws<Claims> parseToken(String token) {
        return Jwts.parser().setSigningKey(generateKey()).parseClaimsJws(token);
    }

    /**
     * 验证token
     * 注意：
     * 退出登录时，要调用下面的removeTokenCache方法移除token的缓存
     * Token过期了，则getUserId-->parseToken方法会抛出异常
     * @param token
     * @return
     */
    public static boolean verifyToken(String token) {
        String userId = getUserId(token);
        String cachedToken = getCachedToken(userId);
        return cachedToken != null && cachedToken.equals(token);
    }

    /**
     * 验证token
     * @param token
     * @return
     */
    public static String refreshToken(String token) {
        return createToken(getUserId(token));
    }

    /**
     * 获取用户id
     * @param token
     * @return
     */
    public static String getUserId(String token) {
        Jws<Claims> jws = parseToken(token);
        return jws.getBody().getSubject();
    }

    /**
     * 获取token的id
     * @param token
     * @return
     */
    public static String getId(String token) {
        Jws<Claims> jws = parseToken(token);
        return jws.getBody().getId();
    }

    /**
     * 从请求中获取token
     * @return
     */
    public static String getToken() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return getToken(request);
    }

    /**
     * 从请求中获取token
     * @return
     */
    public static String getToken(HttpServletRequest request) {
        // 1. 从cookie中获取
        Cookie[] cookies = request.getCookies();
        if (cookies != null && cookies.length > 0) {
            for (Cookie cookie : cookies) {
                if (AppConstant.HEADER_TOKEN_KEY.equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }

        // 2. 从header中获取
        String token = request.getHeader(AppConstant.HEADER_TOKEN_KEY);
        if (StrUtil.isNotEmpty(token)){
            return token;
        }

        // 3. 从url中获取
        return request.getParameter(AppConstant.QUERY_TOKEN_KEY);
    }

    public static LoginUser getLoginUser() {
        return UserContextHolder.get();
    }

    /**
     * 获取登录用户id
     * @return
     */
    public static String getLoginUserId() {
        LoginUser loginUser = getLoginUser();
        return loginUser != null ? loginUser.getUserId() : null;
    }

    /**
     * 获取登录用户或者抛异常
     * @return
     */
    public static LoginUser getLoginUserOrException() {
        LoginUser loginUser = getLoginUser();
        if (loginUser == null) {
            throw new UnifiedException(StatusMessageEnum.UN_LOGIN);
        }
        return loginUser;
    }

    /**
     * 获取登录用户id或者抛异常
     * @return
     */
    public static String getLoginUserIdOrException() {
        return getLoginUserOrException().getUserId();
    }

    /**
     * 获取登录用户信息
     * @param token
     * @return
     */
    public static LoginUser getLoginUser(String token) {
        String userId = getUserId(token);
        User user = UserCacheManager.INSTANCE.getById(Integer.parseInt(userId));
        Assert.notNull(user, "用户不存在");
        return new DefaultLoginUser(user);
    }


    public static void main(String[] args) {
        LoginUtil.author = "dxy";
        LoginUtil.module = "hms";
        LoginUtil.site = "dxy.com";
        LoginUtil.issuer = "dxy";
        LoginUtil.secret = "fjioahgajgi";
        LoginUtil.expire = 120;
        String token = LoginUtil.createToken("123456");
        // String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTYiLCJhdXRob3IiOiJkeHkiLCJtb2R1bGUiOiJobXMiLCJpc3MiOiJkeHkiLCJleHAiOjE3MjcxODg3NjYsImlhdCI6MTcyNzE4ODcwNiwianRpIjoiMjAwOWM1YzZlMGY4NDhmM2JmOGZlZjMwZmE1YjBiZDMifQ.k1Va6zLwyCwEfbdPiUNSZs3cOMXAdKZuudbOKat1jbw";
        System.out.println("token: " + token);
        System.out.println("verify: " + LoginUtil.verifyToken(token));
        System.out.println("userId: " + LoginUtil.getUserId(token));
        System.out.println("jti: " + LoginUtil.getId(token));
    }

}
